﻿using System.Collections.ObjectModel;
using FRRJIf;
using RJi.FanucInterface3.Common.Enum;
using RJi.FanucInterface3.DataParser.Internal;

namespace RJi.FanucInterface3.Common.Internal;

internal class Reader(
    Core core,
    Dictionary<string, VariableMetadata> primaryTableMetadataCache,
    Dictionary<string, VariableMetadata> secondTableMetadataCache,
     Dictionary<string, VariableMetadata> inputMetadataCache,
      Dictionary<string, VariableMetadata> outputMetadataCache) : IReader
{
    private readonly Dictionary<string, object> _inputVariables = [];
    private readonly Dictionary<string, object> _outputVariables = [];
    private readonly Dictionary<string, object> _primaryTableVariables = [];
    private readonly Dictionary<string, object> _secondTableVariables = [];

    public IReadOnlyDictionary<string, object> InputVariables
    {
        get { return new ReadOnlyDictionary<string, object>(_inputVariables); }
    }

    public IReadOnlyDictionary<string, object> OutputVariables
    {
        get { return new ReadOnlyDictionary<string, object>(_outputVariables); }
    }

    public IReadOnlyDictionary<string, object> PrimaryTableVariables
    {
        get { return new ReadOnlyDictionary<string, object>(_primaryTableVariables); }
    }

    public IReadOnlyDictionary<string, object> SecondTableVariables
    {
        get { return new ReadOnlyDictionary<string, object>(_secondTableVariables); }
    }

    public object? ReadInput(InputType type, int startIndex = 1, int count = 1)
    {
        return type switch
        {
            InputType.Ai => ReadAi(startIndex, count),
            InputType.Di => ReadDi(startIndex, count),
            InputType.Gi => ReadGi(startIndex, count),
            InputType.Ri => ReadRi(startIndex, count),//1-8
            InputType.Si => ReadSi(startIndex, count),//0-15
            InputType.Ui => ReadUi(startIndex, count),//1-20
            _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
        };
    }

    public object? ReadOutput(OutputType type, int startIndex = 1, int count = 1)
    {
        return type switch
        {
            OutputType.Ao => ReadAo(startIndex, count),
            OutputType.Do => ReadDo(startIndex, count),
            OutputType.Go => ReadGo(startIndex, count),
            OutputType.Ro => ReadRo(startIndex, count),
            OutputType.So => ReadSo(startIndex, count),
            OutputType.Uo => ReadUo(startIndex, count),
            _ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
        };
    }

    public void UpdateInputVariables()
    {
        foreach (var item in inputMetadataCache)
        {
            if (item.Value.DataType==typeof(InputType))
            {
                var value = ReadInput((InputType)item.Value.VariableRef, item.Value.StartIndex, item.Value.Count);
                _inputVariables[item.Key]=value!;
            }
        }
    }

    public void UpdateOutputVariables()
    {
        foreach (var item in outputMetadataCache)
        {
            if (item.Value.DataType==typeof(OutputType))
            {
                var value = ReadOutput((OutputType)item.Value.VariableRef, item.Value.StartIndex, item.Value.Count);
                _outputVariables[item.Key]=value!;
            }
        }
    }

    public void UpdatePrimaryTableVariables()
    {
        foreach (var item in primaryTableMetadataCache)
        {
            var parser = ParserFactory.GetParser(item.Value);
            var value = parser.Parse(item.Value);
            _primaryTableVariables[item.Key]=value!;
        }
    }

    public void UpdateSecondTableVariables()
    {
        foreach (var item in secondTableMetadataCache)
        {
            var parser = ParserFactory.GetParser(item.Value);
            var value = parser.Parse(item.Value);
            _secondTableVariables[item.Key]=value!;
        }
    }

    #region Input/Output

    private object? ReadAi(int startIndex = 1, int count = 64)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 64-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array gi = new int[count];
        var result = core.ReadGI(startIndex+1000, ref gi, count);
        if (!result) return null;
        return count==1 ? ((int[])gi)[0] : Array.AsReadOnly((int[])gi);
    }

    private object? ReadAo(int startIndex = 1, int count = 64)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 64-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array go = new int[count];
        var result = core.ReadGO(startIndex+1000, ref go, count);
        if (!result) return null;
        return count==1 ? ((int[])go)[0] : Array.AsReadOnly((int[])go);
    }

    private object? ReadDi(int startIndex = 1, int count = 512)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 512-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array sdi = new short[count];
        var result = core.ReadSDI(startIndex, ref sdi, count);
        if (!result) return null;
        return count==1 ? ((short[])sdi)[0] : Array.AsReadOnly((short[])sdi);
    }

    private object? ReadDo(int startIndex = 1, int count = 512)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 512-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array sdo = new short[count];
        var result = core.ReadSDO(startIndex, ref sdo, count);
        if (!result) return null;
        return count==1 ? ((short[])sdo)[0] : Array.AsReadOnly((short[])sdo);
    }

    private object? ReadGi(int startIndex = 1, int count = 300)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 300-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array gi = new int[count];
        var result = core.ReadGI(startIndex, ref gi, count);
        if (!result) return null;
        return count==1 ? ((int[])gi)[0] : Array.AsReadOnly((int[])gi);
    }

    private object? ReadGo(int startIndex = 1, int count = 300)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 300-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array go = new int[count];
        var result = core.ReadGO(startIndex, ref go, count);
        if (!result) return null;
        return count==1 ? ((int[])go)[0] : Array.AsReadOnly((int[])go);
    }

    private object? ReadRi(int startIndex = 1, int count = 8)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 8-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array ri = new short[count];
        var result = core.ReadRDI(startIndex, ref ri, count);
        return result ? Array.AsReadOnly((short[])ri) : null;
    }

    private object? ReadRo(int startIndex = 1, int count = 8)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 8-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array ro = new short[count];
        var result = core.ReadRDO(startIndex, ref ro, count);
        return result ? Array.AsReadOnly((short[])ro) : null;
    }

    private object? ReadSi(int startIndex = 0, int count = 15)
    {
        startIndex=Math.Max(0, startIndex);
        var maxAvailable = 15-startIndex+1;
        count=Math.Clamp(count, 0, maxAvailable);

        Array si = new short[count+1];
        var result = core.ReadSI(startIndex, ref si, count);
        return result ? Array.AsReadOnly((short[])si) : null;
    }

    private object? ReadSo(int startIndex = 0, int count = 15)
    {
        startIndex=Math.Max(0, startIndex);
        var maxAvailable = 15-startIndex+1;
        count=Math.Clamp(count, 0, maxAvailable);

        Array so = new short[count+1];
        var result = core.ReadSO(startIndex, ref so, count);
        return result ? Array.AsReadOnly((short[])so) : null;
    }

    private object? ReadUi(int startIndex = 1, int count = 20)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 20-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array ui = new short[count];
        var result = core.ReadUI(startIndex, ref ui, count);
        return result ? Array.AsReadOnly((short[])ui) : null;
    }

    private object? ReadUo(int startIndex = 1, int count = 20)
    {
        startIndex=Math.Max(1, startIndex);
        var maxAvailable = 20-startIndex+1;
        count=Math.Clamp(count, 1, maxAvailable);

        Array uo = new short[count];
        var result = core.ReadUO(startIndex, ref uo, count);
        return result ? Array.AsReadOnly((short[])uo) : null;
    }

    #endregion Input/Output
}